
#include <Esp8266Gzip.h>

/*
 * 2023.7.10 zhb1190
 * 1、增加对gzip响应的处理  
 * 2、修改气象站源码的稳定性
 * 
 * 
 * 
0.96 气象站 第五版 配套源码

需要配合wifi_link_tool配网工具 地址：https://github.com/bilibilifmk/wifi_link_tool
所需库：
u8g2
ArduinoJson
time(以提供)
by：发明控
*/
//#define SERIAL_DEBUG
#define FS_CONFIG
//兼容1.1.x版本库
#include <wifi_link_tool.h>
#include <ArduinoJson.h>
#include <Arduino.h>

#ifndef SERIAL_DEBUG
#include <U8g2lib.h>
#include "zlt.h"
#endif

#include <WiFiClientSecure.h>

#include <Time.h>
#include <TimeLib.h>
#include <WiFiUdp.h>
#include <WiFiClientSecure.h>

#include <WiFiClientSecureBearSSL.h>

#ifdef U8X8_HAVE_HW_SPI
#include <SPI.h>
#endif
#ifdef U8X8_HAVE_HW_I2C
#include <Wire.h>
#endif

String  keys  = "xxxxx";  // 接口地址：https://console.qweather.com/app/index
String  dq  = "xxxx"; //填入城市编号  获取编号 https://where.qweather.com/index.html

static const uint8_t D1   = 1;
static const uint8_t D2   = 3;
static const uint8_t D3   = 0;
static const uint8_t D4   = 2;

#define sck D1 /* 屏幕 */
#define sda D2

const int CONFIG_HTTPPORT   = 80;
const int CONFIG_HTTPSPORT  = 443;

//const uint8_t fingerprint[20] = { 0xB8, 0x93, 0x1E, 0xBD, 0xCB, 0xCD, 0xCE, 0xE5, 0xC5, 0x9F, 0x08, 0x43, 0xA8, 0x6C, 0x8C, 0xD0, 0xF7, 0xCB, 0x1E, 0x42 };

#ifndef SERIAL_DEBUG
U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2( U8G2_R0, /* clock=*/ sck, /* data=*/ sda, /* reset=*/ U8X8_PIN_NONE );
#endif
int             col   = 999;
String          diqu    = "";
int             tq    = 0;
String          pm2   = "";
String          wendu   = "";
String          tim   = "00:00";
String          dat   = "2019/01/01";
unsigned long     previousMillis  = 0;
const long        interval  = 30000;        /* 30秒更新屏幕 */
unsigned long     previousMillis2 = 0;
const long        interval2 =1800000;      /* 半小时更新天气  */
int         one   = 0;
int pcboot=0; //pc请求识别
IPAddress timeServer( 120, 25, 115, 20 );                         /* 阿里云ntp服务器 如果失效可以使用 120.25.115.19   120.25.115.20 */
#define STD_TIMEZONE_OFFSET +8                                     /* 设置中国 */
const int timeZone = 8;                                            /* 修改北京时区 */
WiFiUDP   Udp;

unsigned int  localPort = 8266;                                 /* 修改udp 有些路由器端口冲突时修改 */
int   servoLift = 1500;
        
void setup()
{
  WiFiServer    HTTPserver( CONFIG_HTTPPORT );
  WiFiServerSecure  HTTPSserver( CONFIG_HTTPSPORT );

#ifdef SERIAL_DEBUG
  Serial.begin( 115200 );
#endif
  /*
   * 开启串口
   * ///////////////////////////////////////////////////////基础设置//////////////////////////////////////////////////////////
   */

  rstb = D3;
  /* 重置io */
  stateled = D4;
  /* 指示灯io */
  Hostname = "oled气象站";
  /* 设备名称 允许中文名称 不建议太长 */
  wxscan = true;
  /* 是否被小程序发现设备 开启意味该设备具有后台 true开启 false关闭 */
#ifndef SERIAL_DEBUG
  u8g2.begin();
  u8g2.enableUTF8Print();
  u8g2.setFont( u8g2_font_unifont_t_chinese2 );
  u8g2.setFontDirection( 0 );
  u8g2.clearBuffer();
  u8g2.setCursor( 0, 15 );
  u8g2.print( "开机" );
  u8g2.setCursor( 0, 30 );
  u8g2.print( "WiFi to Link" );
  u8g2.sendBuffer();
#endif
  webServer.on("/pc", pc);//pc请求
  load();
  /* 初始化WiFi link tool */
#ifdef SERIAL_DEBUG
  Serial.println( "设置 UDP" );
#endif
  Udp.begin( localPort );
#ifdef SERIAL_DEBUG
  Serial.print( "Local port: " );
  Serial.println( Udp.localPort() );
  Serial.println( "正在等待同步" );
#endif
  if ( link() )
  {
#ifndef SERIAL_DEBUG
    u8g2.setFont( u8g2_font_unifont_t_chinese2 );
    u8g2.setFontDirection( 0 );
    u8g2.clearBuffer();
    u8g2.setCursor( 0, 15 );
    u8g2.print( "开机" );
    u8g2.setCursor( 0, 30 );
    u8g2.print( "WiFi to Link" );
    u8g2.setCursor( 0, 45 );
    u8g2.print( "OK" );
    u8g2.setCursor( 0, 60 );
    u8g2.print( WiFi.localIP() );
    u8g2.sendBuffer();
#endif
    sjfx();
    delay( 1000 );
#ifndef SERIAL_DEBUG
    u8g2.sendBuffer();
    u8g2.clearBuffer();
    u8g2.drawXBMP(0,0,128,64,logo);
    u8g2.sendBuffer();
#endif
    delay(1000);
  
    setSyncProvider( getNtpTime );
    shuaxin();
  }else{
#ifndef SERIAL_DEBUG
    u8g2.setFont( u8g2_font_unifont_t_chinese2 );
    u8g2.setFontDirection( 0 );
    u8g2.clearBuffer();
    u8g2.setCursor( 45, 15 );
    u8g2.print( "配网" );
    u8g2.setCursor( 0, 30 );
    u8g2.print( "AP TO Link:" );
    u8g2.setCursor( 0, 45 );
    u8g2.print( "wifi_link_tool" );
    u8g2.setCursor( 0, 60 );
    u8g2.print( "IP:6.6.6.6" );
    u8g2.sendBuffer();
#endif
    delay( 5000 );
#ifndef SERIAL_DEBUG
    u8g2.clearBuffer();
    u8g2.drawXBMP( 0, 0, 128, 64, wx );
    u8g2.sendBuffer();
#endif
  }
}


void loop()
{
  pant(); /* WiFi link tool 服务维持函数  请勿修改位置 */
  if ( link() )
  {
    unsigned long currentMillis = millis();
    if ( currentMillis - previousMillis >= interval )
    {
      previousMillis = currentMillis;
      shuaxin();
      //xx();
    }
    unsigned long currentMillis2 = millis();
    if ( currentMillis2 - previousMillis2 >= interval2 )
    {
      previousMillis2 = currentMillis2;
      one   = 0;
      sjfx();
    }
  }
}


void sjfx()
{
  xx();
  delay( 1000 );
  pm();
  delay( 1000 );
  col = tq;
#ifdef SERIAL_DEBUG
  Serial.println( "当前地区： " + diqu );
  Serial.println( "天气代码： " + tq);
  Serial.println( "体感温度： " + wendu );
  Serial.println( "PM2.5： " + pm2 );
#endif
}


void shuaxin() {
  if(pcboot==0) {
    {
      String zov = "";
      if (hour() < 10) {
        zov = "0";
      }
      
      if (minute() < 10) {
        tim = zov + String( hour() ) + ":0" + String( minute() );
      }else{ 
        tim = zov + String( hour() ) + ":" + String( minute() ); 
      }
      
      dat = String( year() ) + "/" + String( month() ) + "/" + String( day() );
#ifdef SERIAL_DEBUG
      Serial.print( tim );    /* 输出当前网络分钟 */
      Serial.print(" ");
      Serial.print( dat );    /* 输出当前日期 */
      Serial.println();
#endif
#ifndef SERIAL_DEBUG
      u8g2.clearBuffer();
#endif
    }
/*  */
    tubiao();
#ifndef SERIAL_DEBUG
    {
      u8g2.setFont(u8g2_font_ncenB18_tf);  
      u8g2.setFontDirection(0); 
      u8g2.setCursor(65,53);
      u8g2.print(wendu);   //温度
      u8g2.setFont(u8g2_font_6x10_tf);  
      u8g2.setFontDirection(0);
      u8g2.setCursor(60,64);
      u8g2.print(dat);  //日期
      u8g2.drawXBMP(100,32,25,25,col_ssd);
      u8g2.setFont(u8g2_font_ncenR12_tf);  
      u8g2.setFontDirection(0);  
      u8g2.setCursor(0,48);
      u8g2.print("pm2.5");
      u8g2.setCursor(10,62);
      u8g2.print(pm2); //pm2.5 浓度
      u8g2.setFont(u8g2_font_fub25_tf);  
      u8g2.setFontDirection(0);
      u8g2.setCursor(40,32);
      u8g2.print(tim);
      u8g2.sendBuffer(); 
    }
#endif
  } else {
    pcboot=0;
  }
}

void tubiao( ){
#ifndef SERIAL_DEBUG
  const unsigned char* icon = col_999;
  if (col==100||col==150){  //晴      
    icon = col_100;   
  }

  else if (col==102||col==101){        
    icon = col_102;  
  }else  if (col==103||col==153){        
    icon = col_103;//晴间多云 
  }else if (col==104||col==154){        
    icon = col_104;//阴    
  }

  else if (col>=300&&col<=301){        
    icon = col_301;//阵雨   
  }else if (col>=302&&col<=303){        
    icon = col_302;//雷阵雨   
  }else if (col==304){        
    icon = col_304;//冰雹  
  }else if (col==399||col==314||col==305||col==306||col==307||col==315||col==350||col==351){        
    icon = col_307;//雨   
  }

  else if ((col>=308&&col<=313)||(col>=316&&col<=318)){        
    icon = col_310;//暴雨    
  }else if ((col>=402&&col<=406)||col==409||col==410||col==400||col==401||col==408||col==499||col==456){        
    icon = col_401;//雪  
  }else if (col==407||col==457){        
    icon = col_407;//阵雪  
  }else if (col>=500&&col<=502){        
    icon = col_500;//雾霾  
  }else if (col>=503&&col<=508){        
    icon = col_503;//沙尘暴 
  }else if (col>=509&&col<=515){        
    icon = col_509;;//不适宜生存  
  }   
  u8g2.drawXBMP(0,0,40,40,icon);


#endif
}


void pc() {
  #if 0
  pcboot=1;
  String clk=webServer.arg("clk");
  String cpu=webServer.arg("cpu");
  String ram=webServer.arg("ram");
  String cput=webServer.arg("cput");
  webServer.arg("cpuv");
#ifndef SERIAL_DEBUG
  u8g2.clearBuffer();
  u8g2.drawXBMP(0,0,128,64,pctp);
  u8g2.setFont(u8g2_font_crox5hb_tf);  
  u8g2.setFontDirection(0); 
  u8g2.setCursor(33,25);
  u8g2.print(cpu);  //cpu
  u8g2.setCursor(33,57);
  u8g2.print(cput); //cput
  u8g2.setCursor(95,57);
  u8g2.print(ram); //ram
  u8g2.setFont(u8g2_font_ncenR10_tf);  
  u8g2.setFontDirection(0); 
  u8g2.setCursor(96,16);
  u8g2.print(clk); //mhz
  u8g2.setCursor(94,30);
  u8g2.print("MHz"); 

  u8g2.sendBuffer();   
#endif
  webServer.send(200, "text/plain", "ojbk");
  #endif
}
  

/* //////////////////////////////////////////////////天气数据 */
void xx()
{
  uint32_t outlen = 512;
  uint8_t* buff = (uint8_t*)malloc(outlen);
  if(!buff) {
#ifdef SERIAL_DEBUG
    Serial.println("malloc memory failed");
#endif
    return;
  }

  if(https_get_gzip("https://devapi.qweather.net/v7/weather/now?gzip=n&location="+dq+"&key="+keys,
    buff,
    outlen)) {
#ifdef SERIAL_DEBUG
    Serial.printf("Get Data %s\n", buff);
#endif
  } else {
    free(buff);
    return;
  }

  DynamicJsonBuffer jsonBuffer(1024);
  JsonObject& res_json = jsonBuffer.parseObject(buff);
  String r1=res_json["basic"]["location"];//地区
  int r2=res_json["now"]["icon"];//天气
  String r3=res_json["now"]["feelsLike"];//体感温度
  jsonBuffer.clear();
  diqu  = r1; /* 地区 */
  tq  = r2; /* 天气 */
  wendu = r3.toInt(); /* 体感温度 */
   
  free(buff);
}


void pm()
{
  uint32_t outlen = 1024;
  uint8_t* buff = (uint8_t*)malloc(outlen);
  if(!buff) {
#ifdef SERIAL_DEBUG
    Serial.println("malloc memory failed");
#endif
    return;
  }

  if(https_get_gzip("https://devapi.qweather.net/v7/air/now?gzip=n&location="+dq+"&key="+keys,
    buff,
    outlen)) {
#ifdef SERIAL_DEBUG
    Serial.printf("Get Data %s\n", buff);
#endif
  } else {
    free(buff);
    return;
  }

  DynamicJsonBuffer jsonBuffer(1024);
  JsonObject& res_json = jsonBuffer.parseObject(buff);
  String r1=res_json["now"]["pm2p5"];//pm25
  jsonBuffer.clear();
  if ( r1 != "" )
  {
    pm2 = r1;                                                                               /* pm2.5 */
  }
  free(buff);
}

/* ////////////////////////////////////////////////////////////////解包设置////////////////////////////////////////////////////////////////////////////////////////////////////////////// */
void digitalClockDisplay()
{
  /*  */
#ifdef SERIAL_DEBUG
  Serial.print( hour() );
#endif
  printDigits( minute() );
#ifdef SERIAL_DEBUG
  Serial.println();
#endif
}


void printDigits( int digits )
{
#ifdef SERIAL_DEBUG
  Serial.print( ":" );
#endif
  if ( digits < 10 ) {
#ifdef SERIAL_DEBUG
    Serial.print( '0' );
#endif
  }
#ifdef SERIAL_DEBUG
  Serial.print( digits );
#endif
}


bool https_get_gzip(String url, uint8_t* outbuf, uint32_t& outlen)
{
  uint8_t* buff = 0;
  int readed = 0;
  {
    std::unique_ptr<BearSSL::WiFiClientSecure>client(new BearSSL::WiFiClientSecure);  
    client->setInsecure();  
    HTTPClient https;  
#ifdef SERIAL_DEBUG
    Serial.println("HTTP begin");
#endif
    if (https.begin(*client, url))
    {  
#ifdef SERIAL_DEBUG
      Serial.println("HTTP GET...");
#endif
      int httpCode = https.GET();  
#ifdef SERIAL_DEBUG
      Serial.printf("HTTP Code %d\n", httpCode);
#endif
      int len = 0;
      if (httpCode == HTTP_CODE_OK) {  
        len = https.getSize();
#ifdef SERIAL_DEBUG
        Serial.printf("HTTP body len %d\n", len);  
#endif
      } else {
#ifdef SERIAL_DEBUG
        Serial.println("HTTP GET error");
#endif
        https.end();
        return false;
      }
      buff = (uint8_t*)malloc(sizeof(uint8_t)*(len+1));
      memset(buff, 0, len+1);
      while (https.connected() && (len > 0 || len == -1)) {
        // read up to 128 byte
        int c = client->readBytes(buff+readed, std::min((size_t)len, (size_t)(len-1-readed)));
#ifdef SERIAL_DEBUG
        Serial.printf("readBytes: %d\n", c);
#endif
        if (!c) { 
#ifdef SERIAL_DEBUG
          Serial.println("read timeout"); 
#endif
          readed = 0;
          break;
        }
        readed+=c;
        if (len > 0) { len -= c; }
      }
#ifdef SERIAL_DEBUG
      Serial.printf("HTTP buff len %d\n", readed);
#endif
      https.end();
    } else {  
#ifdef SERIAL_DEBUG
      Serial.printf("[HTTPS]请求链接失败\n");  
#endif
      return false;
    } 
  }
  if(outlen<=readed) {
#ifdef SERIAL_DEBUG
    Serial.println("out buf is too small");
#endif
    return false;
  }
  if(readed) {
    memset(outbuf, 0, outlen);
    
    int result = 0;
#ifdef SERIAL_DEBUG
    Serial.println(ESP.getFreeHeap());
#endif

    result=Esp8266Gzip::decompress_gzip(buff, readed, outbuf, outlen,outlen);
    if(result>0){
      //Serial.write(outbuf,outlen);
    } else {
#ifdef SERIAL_DEBUG
      Serial.printf("gzip decode ret %d\n", result);
#endif
      outlen = 0;
      return false;
    }
    free(buff);
    return true;
  }
  return false;
}


const int NTP_PACKET_SIZE = 48;
byte    packetBuffer[NTP_PACKET_SIZE];

time_t getNtpTime()
{
  while ( Udp.parsePacket() > 0 )
    ;
#ifdef SERIAL_DEBUG
  Serial.println( "连接时间服务器" );
#endif
  sendNTPpacket( timeServer );
  uint32_t beginWait = millis();
  while ( millis() - beginWait < 1500 )
  {
    int size = Udp.parsePacket();
    if ( size >= NTP_PACKET_SIZE )
    {
      Serial.println( "时间服务器应答" );
      Udp.read( packetBuffer, NTP_PACKET_SIZE );
      unsigned long secsSince1900;
      /* convert four bytes starting at location 40 to a long integer */
      secsSince1900 = (unsigned long) packetBuffer[40] << 24;
      secsSince1900 |= (unsigned long) packetBuffer[41] << 16;
      secsSince1900 |= (unsigned long) packetBuffer[42] << 8;
      secsSince1900 |= (unsigned long) packetBuffer[43];
      return(secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR);
    }
  }
#ifdef SERIAL_DEBUG
  Serial.println( "No NTP Response :-(" );
#endif
  return(0);
}


void sendNTPpacket( IPAddress &address )
{
  memset( packetBuffer, 0, NTP_PACKET_SIZE );

  packetBuffer[0] = 0b11100011;
  packetBuffer[1] = 0;
  packetBuffer[3] = 0xEC;

  packetBuffer[12]  = 49;
  packetBuffer[13]  = 0x4E;
  packetBuffer[14]  = 49;
  packetBuffer[15]  = 52;
  Udp.beginPacket( address, 123 );
  Udp.write( packetBuffer, NTP_PACKET_SIZE );
  Udp.endPacket();
}
